/**
 ***************************************************************************************************
 *
 * @file ble_tys.c
 *
 * @brief Tuya service transport profile implementation.
 *
 * Copyright (C) Eker 2021
 *
 *
 ***************************************************************************************************
 */

/**
 ***************************************************************************************************
 * @addtogroup TYS
 * @{
 ***************************************************************************************************
 */

/*
 * INCLUDE FILES
 ***************************************************************************************************
 */
#include "rwip_config.h"

#include "gap.h"
#include "gattc_task.h"
#include "attm.h"
#include "gapc_task.h"
#include "prf_utils.h"

#include "ke_mem.h"
#include "co_utils.h"
#include "co.h"
#include "ble_tys.h"
#include "ble_tys_task.h"

#define APP_LOG_DOMAIN      "tys"
#define APP_LOG_LEVEL       APP_LOG_LEVEL_TYS
#include "app_log.h"

/*
 * TY PROFILE ATTRIBUTES
 ***************************************************************************************************
 */
static const uint8_t ty_service_uuid128[16] = {
    0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x50, 0xfD, 0x00, 0x00};

#define TYS_IDX_REV1_VAL_128   {0xD0, 0x07, 0x9B, 0x5F, 0x80, 0x00, 0x01, 0x80, \
                                  0x01, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
#define TYS_IDX_UPLOAD_VAL_128 {0xD0, 0x07, 0x9B, 0x5F, 0x80, 0x00, 0x01, 0x80, \
                                  0x01, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}
#define TYS_IDX_REV2_VAL_128   {0xD0, 0x07, 0x9B, 0x5F, 0x80, 0x00, 0x01, 0x80, \
                                  0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00}

const struct attm_desc_128 tys_att_db_128[TYS_IDX_NB] =
{
    // Service Declaration
    [TYS_IDX_SVC]        =   {ATT_16_TO_128_ARRAY(ATT_DECL_PRIMARY_SERVICE), PERM(RD, ENABLE), 0, 0},
    
    // Write Characteristic Declaration
    [TYS_IDX_WRITE_CHAR] =   {ATT_16_TO_128_ARRAY(ATT_DECL_CHARACTERISTIC), PERM(RD, ENABLE), 0, 0},
    // Write Characteristic Value
    [TYS_IDX_WRITE_VAL]  =   {TYS_IDX_REV1_VAL_128, PERM(WRITE_COMMAND, ENABLE),
                              PERM_VAL(UUID_LEN, PERM_UUID_128), TYS_UPLOAD_MAX_LEN},

    // Notify Characteristic Declaration
    [TYS_IDX_NTF_CHAR]   =   {ATT_16_TO_128_ARRAY(ATT_DECL_CHARACTERISTIC), PERM(RD, ENABLE), 0, 0},
    // Notify Characteristic Value
    [TYS_IDX_NTF_VAL]    =   {TYS_IDX_UPLOAD_VAL_128, PERM(NTF, ENABLE),
                              PERM(RI, ENABLE)|PERM_VAL(UUID_LEN, PERM_UUID_128), TYS_UPLOAD_MAX_LEN},
    // Notify Characteristic - Client Characteristic Configuration Descriptor
    [TYS_IDX_NTF_CFG]    =   {ATT_16_TO_128_ARRAY(ATT_DESC_CLIENT_CHAR_CFG),
                              PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0, 0},

    // Read Characteristic Declaration
    [TYS_IDX_READ_CHAR]  =   {ATT_16_TO_128_ARRAY(ATT_DECL_CHARACTERISTIC), PERM(RD, ENABLE), 0, 0},
    // Read Characteristic Value
    [TYS_IDX_READ_VAL]   =   {TYS_IDX_REV2_VAL_128, PERM(RD, ENABLE),
                              PERM_VAL(UUID_LEN, PERM_UUID_128), TYS_UPLOAD_MAX_LEN},
};

/**
 ***************************************************************************************************
 * @brief Initialization of the TYS module.
 * This function performs all the initializations of the Profile module.
 *  - Creation of database (if it's a service)
 *  - Allocation of profile required memory
 *  - Initialization of task descriptor to register application
 *      - Task State array
 *      - Number of tasks
 *      - Default task handler
 *
 * @param[out]    env        Collector or Service allocated environment data.
 * @param[in|out] start_hdl  Service start handle (0 - dynamically allocated), only applies for services.
 * @param[in]     app_task   Application task number.
 * @param[in]     sec_lvl    Security level (AUTH, EKS and MI field of @see enum attm_value_perm_mask)
 * @param[in]     param      Configuration parameters of profile collector or service (32 bits aligned)
 *
 * @return status code to know if profile initialization succeed or not.
 ***************************************************************************************************
 */
static uint8_t tys_init(struct prf_task_env* env, uint16_t* start_hdl, uint16_t app_task,
                        uint8_t sec_lvl, struct ty_server_db_cfg* params)
{
    // -------------------- create the attribute database for the profile --------------------
    //Database Creation Status
    uint8_t status = ATT_ERR_NO_ERROR;
    uint16_t buffer_size=0;
    uint16_t max_count=TYS_IDX_MAX;

    struct attm_desc_128 temp_128_db[TYS_IDX_NB];

    if (params == NULL || params->fifo_size <= 0)
    {
        buffer_size = 2048;
    }
    else
    {
        buffer_size = params->fifo_size;
    }
    if (params->connect_num < TYS_IDX_MAX)
    {
        max_count = params->connect_num;
    }

    if (params != NULL)
    {
        status = attm_svc_create_db_128(start_hdl, ty_service_uuid128, NULL,
                    TYS_IDX_NB, NULL, env->task, &tys_att_db_128[0],
                    (sec_lvl & (PERM_MASK_SVC_DIS | PERM_MASK_SVC_AUTH | PERM_MASK_SVC_EKS)) |
                    PERM(SVC_MI, DISABLE) | PERM(SVC_UUID_LEN, UUID_128));
    }

    // -------------------- allocate memory required for the profile --------------------
    if (status == ATT_ERR_NO_ERROR)
    {
        // allocate TYS required environment variable
        struct tys_env_tag* tys_env =
                (struct tys_env_tag* ) ke_malloc((sizeof(struct tys_env_tag)), KE_MEM_ATT_DB);

        // initialize TYS environment
        env->env           = (prf_env_t*) tys_env;
        tys_env->start_hdl = *start_hdl;
        tys_env->operation = NULL;
        tys_env->prf_env.app_task = app_task | (PERM_GET(sec_lvl, SVC_MI)
                                    ? PERM(PRF_MI, ENABLE) : PERM(PRF_MI, DISABLE));
        tys_env->prf_env.prf_task = env->task | PERM(PRF_MI, DISABLE);
        tys_env->send_pool_size = buffer_size;
        tys_env->send_pool_num = max_count;

        for(uint8_t num = 0; num < max_count ; num++)
        {
            if(params->fifo_buffer)
            {
                tys_env->send_pool[num] = params->fifo_buffer;
                tys_env->send_pool_inner[num] = 0;
            }
            else
            {
                tys_env->send_pool[num] = ke_malloc(tys_env->send_pool_size, KE_MEM_ATT_DB);
                tys_env->send_pool_inner[num] = 1;
            }
            co_fifo_init(&tys_env->send_fifo[num], tys_env->send_pool[num], tys_env->send_pool_size);
        }
        
        // initialize environment variable
        env->id = TASK_ID_TYS;
        ty_server_task_init(&(env->desc));

        for(uint8_t idx = 0; idx < TYS_IDX_MAX ; idx++)
        {
            ke_state_set(KE_BUILD_ID(env->task, idx), TYS_IDLE);
        }
    }

    return (status);
}

/**
 ***************************************************************************************************
 * @brief Destruction of the TYS module - due to a reset for instance.
 * This function clean-up allocated memory (attribute database is destroyed by another
 * procedure)
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 ***************************************************************************************************
 */
static void tys_destroy(struct prf_task_env* env)
{
    struct tys_env_tag* tys_env = (struct tys_env_tag*) env->env;

    // clear on-going operation
    if(tys_env->operation != NULL)
    {
        ke_free(tys_env->operation);
    }

    for(uint8_t num = 0; num < tys_env->send_pool_num; num++)
    {
        if(tys_env->send_pool_inner[num])
            ke_free(tys_env->send_pool[num]);
    }
    env->env = NULL;
    ke_free(tys_env);
}

/**
 ***************************************************************************************************
 * @brief Handles Connection creation
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 ***************************************************************************************************
 */
static void tys_create(struct prf_task_env* env, uint8_t conidx)
{
    struct tys_env_tag* ty_server_env = (struct tys_env_tag*) env->env;
    ASSERT_ERR(conidx < BLE_CONNECTION_MAX);

    // force notification config to zero when peer device is connected
    ty_server_env->ntf_cfg[conidx] = 0;
    ty_server_env->mtu[conidx] = ATT_DEFAULT_MTU;
    ty_server_env->tx_len[conidx] = BLE_MIN_OCTETS;
    ty_server_env->perfect_once_tx_length[conidx] = ATT_DEFAULT_MTU-3;
    ke_state_set(KE_BUILD_ID(env->task, conidx), TYS_CONNECTED);
}

/**
 ***************************************************************************************************
 * @brief Handles Disconnection
 *
 * @param[in|out]    env        Collector or Service allocated environment data.
 * @param[in]        conidx     Connection index
 * @param[in]        reason     Detach reason
 ***************************************************************************************************
 */
static void tys_cleanup(struct prf_task_env* env, uint8_t conidx, uint8_t reason)
{
    struct tys_env_tag* ty_server_env = (struct tys_env_tag*) env->env;

    ASSERT_ERR(conidx < BLE_CONNECTION_MAX);
    // force notification config to zero when peer device is disconnected
    ty_server_env->ntf_cfg[conidx] = 0;
    ty_server_env->mtu[conidx] = ATT_DEFAULT_MTU;
    for(uint8_t num = 0; num < ty_server_env->send_pool_num; num++)
    {
        if(ty_server_env->send_pool[num] != NULL)
        {
            co_fifo_reset(&(ty_server_env->send_fifo[num]));
        }
    }

    ke_state_set(KE_BUILD_ID(env->task, conidx), TYS_IDLE);
}

/*
 * GLOBAL VARIABLE DEFINITIONS
 ***************************************************************************************************
 */
// TYS Task interface required by profile manager
const struct prf_task_cbs tys_itf =
{
        (prf_init_fnct) tys_init,
        tys_destroy,
        tys_create,
        tys_cleanup,
};

/*
 * GLOBAL FUNCTIONS DEFINITIONS
 ***************************************************************************************************
 */
const struct prf_task_cbs* ty_server_prf_itf_get(void)
{
   return &tys_itf;
}
uint16_t ty_server_get_att_handle(uint8_t att_idx)
{
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    uint16_t handle = ty_server_env->start_hdl + att_idx;
    return handle;
}

uint8_t ty_server_get_att_idx(uint16_t handle, uint8_t *att_idx)
{
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    *att_idx = handle - ty_server_env->start_hdl;
    return ATT_ERR_NO_ERROR;
}

uint16_t ty_server_perfect_once_tx_length(uint16_t mtu, uint16_t mto, uint16_t char_len)
{
    uint16_t mtux = MIN(TYS_MTU_TO_NTF_WRTCMD_LEN(mtu), char_len);
    uint16_t mtox = TYS_MTO_TO_NTF_WRTCMD_LEN(mto);

    return (mtux > mtox) ? (mtox + mto * ((mtux-mtox) / mto)) : (mtux);
}

void ty_server_set_max_mtu(uint8_t conidx, uint16_t mtu)
{
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    ty_server_env->mtu[conidx] = mtu;
    ty_server_env->perfect_once_tx_length[conidx] =
        ty_server_perfect_once_tx_length(mtu, ty_server_env->tx_len[conidx], GAP_MAX_LE_MTU);
    LOG_DBG("Data length: %d", ty_server_env->perfect_once_tx_length[conidx]);
}

void ty_server_set_data_len(uint8_t conidx, uint16_t tx_len)
{
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    ty_server_env->tx_len[conidx] = tx_len;
    ty_server_env->perfect_once_tx_length[conidx] =
        ty_server_perfect_once_tx_length(ty_server_env->mtu[conidx],
                                         ty_server_env->tx_len[conidx],
                                         GAP_MAX_LE_MTU);
    LOG_DBG("Data length: %d", ty_server_env->perfect_once_tx_length[conidx]);
}
